
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include "dht.h"
#include "hashtbl.h"
#include <openssl/sha.h>


#include <sys/ioctl.h>
#include <net/if.h>


struct sockaddr_in this_node_info;
struct sockaddr_in client_addr;

struct qParent x;

int this_node_port_num;

char root_node_ip[64];
int root_node_port;

int this_node_socket;

char send_data [1024] , recv_data[1024];

int sin_size, connected, bytes_recieved, true = 1;

HASHTBL *htEmailEntry;
HASHTBL *htNodeIDAddress;
HASHTBL *htStNodeAddress;

char *searchPre;
char *searchSuc;

int main(int argc, char *argv[])
{
	if (argc < 2) {
		
		printf("Usage: \n %s <localport> <node>\n", argv[0]);
		
	} else {
		
        
        
        if(!(htEmailEntry=hashtbl_create(16, NULL))) {
            fprintf(stderr, "ERROR: hashtbl_create() failed\n");
            exit(EXIT_FAILURE);
        }
        
        if(!(htNodeIDAddress=hashtbl_create(16, NULL))) {
            fprintf(stderr, "ERROR: hashtbl_create() failed\n");
            exit(EXIT_FAILURE);
        }
        
        if(!(htStNodeAddress=hashtbl_create(16, NULL))) {
            fprintf(stderr, "ERROR: hashtbl_create() failed\n");
            exit(EXIT_FAILURE);
        }
        
        
        /*
         Sample Data
         
         hashtbl_insert(htEmailEntry, "test@mail.com", "hello");
         hashtbl_insert(htEmailEntry, "test1@mail.com", "hello1");
         hashtbl_insert(htEmailEntry, "test2@mail.com", "hello2");
         hashtbl_insert(htEmailEntry, "test3@mail.com", "hello3");
         hashtbl_insert(htEmailEntry, "test4@mail.com", "hello4");
         hashtbl_insert(htEmailEntry, "test5@mail.com", "hello5");
         
         searchResult = hashtbl_get(htEmailEntry, "test1@mail.com");
         
         printf("** Result found = %s\n", searchResult);
         */
        
		this_node_port_num = atoi(argv[1]);
		
		printf("This Node Port\t: %i\n", this_node_port_num);
		
        if ( argc == 3 ) {
            
            char node_input[64];
            
            char *node_in;
            
            strncpy(node_input, argv[2], 64-1);
            
            //char *node_input;
            node_in = strtok(node_input, ":");
            
            if(node_in)
            {
                strncpy(root_node_ip, node_in, 64-1);
                printf("Root Node IP\t: %s\n", root_node_ip);
            }
            node_in = strtok(NULL, ":");
            
            if(node_in) {
                root_node_port = atoi(node_in);
                printf("Root Node Port\t: %i\n", root_node_port);
            }
            
            if (strlen(root_node_ip) > 0) {
                
                int o = node_connect(root_node_ip, root_node_port);
                hashtbl_insert(htStNodeAddress, "pre", argv[2]);
                
                searchPre = hashtbl_get(htStNodeAddress, "pre");
                searchSuc = hashtbl_get(htStNodeAddress, "suc");
                
                if (searchPre != NULL) {
                    printf("** Predecessor found = %s\n", searchPre);
                } else {
                    printf("** This is the Start node\n");
                }
                
                if (searchSuc != NULL) {
                    printf("** Successor found = %s\n", searchSuc);
                } else {
                    printf("** No Successor as yet\n");
                }
                
            }
        } 
        //node_add("127.0.0.1:8000");
        
        node_listen(this_node_port_num);
        
        return 0;
		
		
		/*
         if ( node_listen(root_node_port) == 1 ) {
         printf("Listening...\n");	
         } */
		/*
         for(;;) {
         printf("Waiting for Connect to Accept...");
         
         int connectFD = accept(this_node_socket, NULL, NULL);
         if(0 > connectFD) {
         perror("error accept failed");
         exit(EXIT_FAILURE);
         }
         }
         */
        
		//printf("this : %i", node_listen(root_node_port));
		
	    //return 0;
		
        
        
        
	}
    
}


int node_listen(int dst_port) {
	
	if ((this_node_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("Socket Error");
        exit(EXIT_FAILURE);
	}
    
    if (setsockopt(this_node_socket,SOL_SOCKET,SO_REUSEADDR,&true,sizeof(int)) == -1) {
        perror("Setsockopt");
        exit(1);
    }
    
	this_node_info.sin_family = AF_INET; 
	this_node_info.sin_port = htons(dst_port);
	this_node_info.sin_addr.s_addr = INADDR_ANY;
	bzero(&(this_node_info.sin_zero), 8);
	
	if (bind(this_node_socket, (struct sockaddr *) &this_node_info, sizeof(struct sockaddr)) < 0){
        perror("Bind Error");
        exit(EXIT_FAILURE);
	}
	if (listen(this_node_socket, 5) < 0) {
        perror("Listen Error");
        exit(EXIT_FAILURE);
	} 
    
    printf("Listening on port %i\n", dst_port);
    fflush(stdout);
    
    
    /*
     
     - Pseudo Code
     
     dht-put			[1]
        handles dht-put requests and adds key value pair to the hashtable
     dht-get			[2]
        handles dht-get requests and gets value from key value pair hashtable when provided with the key
     dht-rm             [3]
        handles dht-rm requests and removes the value from the key value pair hashtable when provided with the key
     dht-ping           [4]
        handles dht-ping requests and responds if a node is alive
     dht-kill           [5]
        handles dht-kill requests and kills off nodes
     dht-node-finger	[6]
        handles dht-node-finger requests to list finger table of a given node
     dht-ls             [7]
        handles dht-ls requests to list all entries in hashtable of a node
     
     node-join          [8]
        handles node to node joining
     
     
     forward-search     [9]
        if a dht-get search is not found in the current node: 
            look up the successor node:
                search successor node for query;
                    if query is not found and there is a successor node : return [10] + successor node IPaddress:Port
                    if query is found : return [11] + result to root_node
                    if query is not found and there is no successor node : return [12]
     
     not-found-search-next  [10]
        send new forward search containing query [9] to IPaddress:Port returned by previous [9]
            this continues searching other nodes to find key value pair
     
     found-return-result    [11]
        when a [9] command returns a match, we send an [11] back to the root_node
            the root_node then sends the result back to the client
     
     result-not-found       [12]
        when a [9] command reaches back to the root_node and finds no result
            send a [12] signifying not finding a result
     
     */
    
    
    while(1)
    {  
        //printf("Start listening...");
        
        sin_size = sizeof(struct sockaddr_in);
        
        connected = accept(this_node_socket, (struct sockaddr *)&client_addr,&sin_size);
        
        //printf("\n I got a connection from (%i , %d)", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
        
        if (connected >= 0) {
            
            char str[64];
            int stp;
            
            inet_ntop(AF_INET, &(client_addr.sin_addr), str, INET_ADDRSTRLEN);
            stp = htons(client_addr.sin_port);
            
            printf("\nConnection Recieved From : %s %d\n", str, ntohs(client_addr.sin_port));
            
            while (1)
            {   
                /*
                 
                 printf("\n SEND (q or Q to quit) : ");
                 gets(send_data);
                 
                 if (strcmp(send_data , "q") == 0 || strcmp(send_data , "Q") == 0)
                 {
                 send(connected, send_data,strlen(send_data), 0); 
                 close(connected);
                 break;
                 }
                 
                 else
                 send(connected, send_data,strlen(send_data), 0);  
                 
                */
                
                bytes_recieved = recv(connected, recv_data,1024,0);
                
                recv_data[bytes_recieved] = '\0';
                
                char recv_data_dirty[579];
                int recv_cmd_type;
                char recv_dir_id[64];
                char recv_entry[512];
                
                if (bytes_recieved > 0) {
                    printf("** RECIEVED DATA = %s \n" , recv_data);
                    
                    
                    
                    char *rdd_in;
                    
                    strncpy(recv_data_dirty, recv_data, 578);
                    
                    //char *node_input;
                    rdd_in = strtok(recv_data_dirty, "|");
                    
                    if(rdd_in)
                    {
                        recv_cmd_type = atoi(rdd_in);
                        //strncpy(recv_cmd_type, rdd_in, 1);
                        printf("recv cmd type\t: %i\n", recv_cmd_type);
                    }
                    rdd_in = strtok(NULL, "|");
                    
                    if(rdd_in) {
                        //root_node_port = atoi(rdd_in);
                        strncpy(recv_dir_id, rdd_in, 64-1);
                        printf("recv dir id\t: %s\n", recv_dir_id);
                    }
                    
                    rdd_in = strtok(NULL, "|");
                    
                    if(rdd_in) {
                        //root_node_port = atoi(rdd_in);
                        strncpy(recv_entry, rdd_in, 512-1);
                        printf("recv entry\t: %s\n", recv_entry);
                    }
                    
                    printf("** RECIEVED DATA DIRTY= %s \n" , recv_data_dirty);
                    //printf("recv cmd type\t: %i,\n", recv_cmd_type);
                    
                    /*
                     
                     dht-put			1
                     dht-get			2
                     dht-rm             3
                     dht-ping           4	
                     dht-kill           5	
                     dht-node-finger	6
                     dht-ls             7
                     
                     node-join          8
                     */
                    
                    //printf( "\n\n&&&&& %i\n\n", recv_cmd_type);
                    
                    //processing the received commands...
                    
                    if ( recv_cmd_type == 1 ) {
                        //dht-put functions
                        hashtbl_insert(htEmailEntry, recv_dir_id, recv_entry);
                        printf(" Adding %s, %s to hashtable...\n\nListening...\n", recv_dir_id, recv_entry);
                        
                    } else if ( recv_cmd_type == 2 ) {
                        //dht-get functions
                        
                        strncpy(x.q_IP, str, 64-1);
                        x.q_port = this_node_port_num;
                        
                        char *searchResult;
                        searchResult = hashtbl_get(htEmailEntry, recv_dir_id);
                        printf("** Result found = %s\n", searchResult);
                        
                        if (searchResult != NULL) {
                            
                            char sendResult[512];
                            
                            strncpy( sendResult, searchResult, 512-1 );
                            send(connected, sendResult, strlen(sendResult), 0);   
                            printf("** Sent Result back... %s\n", sendResult);
                            
                        } else {
                            
                            /* Older Code
                             
                             send(connected, "-1", strlen("-1"), 0);
                             printf("** Nothing found, sent -1 back");
                             
                             */
                            
                            //close(connected);
                            
                            searchSuc = hashtbl_get(htStNodeAddress, "suc");
                            
                            if (searchSuc != NULL) {
                                printf("** Successor found = %s\n", searchSuc);
                                
                                //char buffer[64];
                                
                                //sprintf(buffer, "|%i", this_node_port_num);
                                
                                size_t len1 = strlen(recv_dir_id);
                                //size_t len2 = strlen(buffer);
                                
                                char *s = malloc(len1+ 2);
                                memcpy(s, "9|", 2);
                                memcpy(s + 2, recv_dir_id, len1);
                                //memcpy(s + 2 + len1, buffer, len2);
                                
                                close(connected);
                                
                                node_send(searchSuc, s);
                        
                            } else {
                                printf("** No Successor as yet sending end of search, no result -1.\n");
                                send(connected, "-1", strlen("-1"), 0);
                                printf("** Nothing found, sent -1 back");
                            }
                            
                            
                        }
                        
                    } else if ( recv_cmd_type == 3) {
                        //dht-rm functions
                        printf("** Removing %s...", recv_dir_id);
                        
                        hashtbl_remove(htEmailEntry, recv_dir_id);
                        
                        char send_rm_success[2];
                        strncpy( send_rm_success, "1" , 1);
                        send(connected, send_rm_success, strlen(send_rm_success), 0);   
                        
                        printf("** Remove Success sent... %s\n", send_rm_success);
                        
                    } else if ( recv_cmd_type == 4 ) {
                        //dht-ping functions
                        char send_ping[2];
                        strncpy( send_ping, "1" , 1);
                        send(connected, send_ping, strlen(send_ping), 0);   
                        printf("** Sent Result back... %s\n", send_ping);
                        
                    } else if ( recv_cmd_type == 5 ) {
                        //dht-kill functions
                        
                    } else if ( recv_cmd_type == 6 ) {
                        //dht-node-finger functions
                        
                    } else if ( recv_cmd_type == 7 ) {
                        //dht-ls functions
                    } else if ( recv_cmd_type == 8 ) {
                        
                        char buffer[128];
                        
                        sprintf(buffer, "%s:%s", str, recv_dir_id);
                        
                        printf("%s\n", buffer);
                        
                        node_add(buffer);
                        
                        
                    } else if (recv_cmd_type == 9 ) {
                        
                        char *searchResult1;
                        searchResult1 = hashtbl_get(htEmailEntry, recv_dir_id);
                        printf("** Result found = %s\n", searchResult1);
                        
                        if (searchResult1 != NULL) {
                            
                            char sendResult[512];
                            
                            strncpy(sendResult, searchResult1, 512-1 );
                            
                            size_t len1 = strlen(sendResult);
                            
                            char *s = malloc(len1+3);
                            
                            memcpy(s, "11|", 3);
                            
                            memcpy(s + 3, sendResult, len1);
                            
                            char bufferAddr[64];
                            
                            sprintf(bufferAddr, "%s:%i", str, stp);
                            
                            
                            node_send(bufferAddr, s);   
                            
                            printf("** Sent Result back... %s\n", s);
                            
                        } else {
                            
                            /* Older Code
                             
                             send(connected, "-1", strlen("-1"), 0);
                             printf("** Nothing found, sent -1 back");
                             
                             */
                            
                            //close(connected);
                            
                            searchSuc = hashtbl_get(htStNodeAddress, "suc");
                            
                            if (searchSuc != NULL) {
                                printf("** Successor found = %s\n", searchSuc);
                                
                                size_t len1 = strlen(recv_dir_id);
                                
                                char *s = malloc(len1+ 2);
                                memcpy(s, "9|", 2);
                                memcpy(s + 2, recv_dir_id, len1);
                                
                                node_send(searchSuc, s);
                                
                            } else {
                                printf("** No Successor as yet sending end of search, no result -1.\n");
                                send(connected, "-1", strlen("-1"), 0);
                                printf("** Nothing found, sent -1 back");
                            }
                            
                            
                        }

                        
                    }
                    
                    //printf("recv cmd type\t: %i\n", recv_cmd_type);
                }
                
                fflush(stdout);
                close(connected);
                break;
            }
        }
    }
    
}

int node_add(char nodeAddress[64]){
    
    int i;
    
    size_t length = sizeof(nodeAddress);
    unsigned char hash[SHA_DIGEST_LENGTH];
    SHA1(nodeAddress, length, hash);
    
    //printf("hash is : %s", hash);
    
    printf("Adding node to finger table : %s", nodeAddress);
    
    // Magic code for making hex hash a into a readable searchable string
    
    char temp[40];
    
    sprintf(temp, "%02x", hash[0]);
    
    for(i = 1; i < 20; i++) {
        sprintf(temp + strlen(temp), "%02x", hash[i]);
    }
    
    printf("\n%s", temp);
    
    hashtbl_insert(htNodeIDAddress, temp, nodeAddress);
    hashtbl_insert(htStNodeAddress, "suc", nodeAddress);
    
    
    
    printf("\nNode added to finger table...\n");
    
    
    searchPre = hashtbl_get(htStNodeAddress, "pre");
    searchSuc = hashtbl_get(htStNodeAddress, "suc");
    
    if (searchPre != NULL) {
        printf("** Predecessor found = %s\n", searchPre);
    } else {
        printf("** This is the Start node\n");
    }
    
    if (searchSuc != NULL) {
        printf("** Successor found = %s\n", searchSuc);
    } else {
        printf("** No Successor as yet\n");
    }
    
    //char *searchResult1;
    //searchResult1 = hashtbl_get(htNodeIDAddress, "a90c80bf0f0fc7f79224cd129783eaa26c358d5b");
    //printf("** Result found = %s\n", searchResult1);
    
    
    /*
     int i;
     
     unsigned char ibuf[] = "test address";
     unsigned char obuf[40];
     
     SHA1(ibuf, strlen(ibuf), obuf);
     
     for(i = 0; i < 40; i++) {
     printf("%02x ", obuf[i]);
     } 
     
     printf("\n");
     */
}

int node_connect(char root_ip[64], int root_port){
    
    //creating socket using TCP
    if ((this_node_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        perror("Socket Error");
        exit(EXIT_FAILURE);
    }
    
    //dht_node_host = gethostbyname(dst_ip);
    
    this_node_info.sin_family = AF_INET; 
    inet_aton(root_ip, &(this_node_info.sin_addr)); 
    this_node_info.sin_port = htons(root_port);
    bzero(&(this_node_info.sin_zero), 8);
    printf("connected...\n");
    
    if (connect(this_node_socket, (struct sockaddr *)&this_node_info,
                sizeof(struct sockaddr)) == -1)
    {
        perror("Connect Error\t");
        exit(1);
    }
    
    char buffer[16];
    
    sprintf(buffer, "%d", this_node_port_num);
    
    size_t len1 = strlen(buffer);
    
    char * s = malloc(len1 + 2);
    
    memcpy(s, "8|", 2);
    memcpy(s + 2, buffer, len1);
    
    //char send_data[2] = "8|";
    
    send(this_node_socket,s,strlen(s), 0);   
    
    printf("Connection established and closed.\n");
    
    close(this_node_socket);
    
}


int node_send(char nodeAddress[64], char send_data[512]){
    
    char dst_ip[64];
    
    int dst_port;
    
    char node_input[64];
    
    char *node_in;
    
    strncpy(node_input, nodeAddress, 64-1);
    
    //char *node_input;
    node_in = strtok(node_input, ":");
    
    if(node_in)
    {
        strncpy(dst_ip, node_in, 64-1);
        printf("Send Node IP\t: %s\n", dst_ip);
    }
    node_in = strtok(NULL, ":");
    
    if(node_in) {
        dst_port = atoi(node_in);
        printf("Send Node Port\t: %i\n", dst_port);
    }
    
    
    
    //creating socket using TCP
    if ((this_node_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        perror("Socket Error");
        exit(EXIT_FAILURE); 
    }
    
    //dht_node_host = gethostbyname(dst_ip);
    
    this_node_info.sin_family = AF_INET; 
    inet_aton(dst_ip, &(this_node_info.sin_addr)); 
    this_node_info.sin_port = htons(dst_port);
    bzero(&(this_node_info.sin_zero), 8);
    printf("connected...\n");
    
    if (connect(this_node_socket, (struct sockaddr *)&this_node_info,
                sizeof(struct sockaddr)) == -1)
    {
        perror("Connect Error\t");
        exit(1);
    }
    
    send(this_node_socket,send_data,strlen(send_data), 0);   
    //close(this_client_socket);
    
    /* while(1)
     {
     
     bytes_recieved=recv(this_client_socket,recv_data,1024,0);
     recv_data[bytes_recieved] = '\0';
     
     if (bytes_recieved > 0) {
     printf("\nRecieved data = %s\n" , recv_data);
     close(this_client_socket);
     
     //printf("\nSEND (q or Q to quit) : ");
     fflush(stdout);
     break;
     }
     */
    //gets(send_data);
    /*    
     if (strcmp(send_data , "q") != 0 && strcmp(send_data , "Q") != 0)
     send(this_client_socket,send_data,strlen(send_data), 0); 
     
     else
     {
     send(this_client_socket,send_data,strlen(send_data), 0);   
     close(this_client_socket);
     break;
     }
     */
    close(this_node_socket);
    printf("closed connection after forwarding query...");
    return 0;
    
}


